﻿using Kimd.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;

namespace Kimd.Communication
{
    public class TCPClient : ISocketClient, IDisposable, ICommunicationShow, ICommunication
    {
        #region Properties
        private bool Disposed { get; set; } = false;
        private readonly object objLock = new object();
        private bool IsDisposed { get; set; } = false;
        
        private ManualResetEvent ReceiveFinishEvent = new ManualResetEvent(true);
        private volatile string receiveDataStr = string.Empty;
        private volatile byte[] receiveDataByte = new byte[] { };
        
        private TcpClient _client;
        /// <summary>
        /// 初始是否连接
        /// </summary>
        public bool IsInitConnect { get; set; }
        /// <summary>
        /// IP
        /// </summary>
        public string IP
        {
            get { return Config.IP; }
        }
        /// <summary>
        /// Port
        /// </summary>
        public int Port
        {
            get { return Config.Port; }
        }
        [XmlIgnore]
        public Encoding ReceiveEncoding
        {
            get
            {
                Encoding encoding = Encoding.Default;
                switch (Config.ReceiveEncoding)
                {
                    case EncodingEnumStr.ASCII:
                        encoding = Encoding.ASCII;
                        break;
                    case EncodingEnumStr.Default:
                        encoding = Encoding.Default;
                        break;
                    case EncodingEnumStr.UTF8:
                        encoding = Encoding.UTF8;
                        break;
                    case EncodingEnumStr.UTF32:
                        encoding = Encoding.UTF32;
                        break;
                    case EncodingEnumStr.Unicode:
                        encoding = Encoding.Unicode;
                        break;
                }
                return encoding;
            }
        }
        /// <summary>
        /// 发送解码
        /// </summary>
        [XmlIgnore]
        public Encoding SendEncoding
        {
            get
            {
                Encoding encoding = Encoding.Default;
                switch (Config.SendEncoding)
                {
                    case EncodingEnumStr.ASCII:
                        encoding = Encoding.ASCII;
                        break;
                    case EncodingEnumStr.Default:
                        encoding = Encoding.Default;
                        break;
                    case EncodingEnumStr.UTF8:
                        encoding = Encoding.UTF8;
                        break;
                    case EncodingEnumStr.UTF32:
                        encoding = Encoding.UTF32;
                        break;
                    case EncodingEnumStr.Unicode:
                        encoding = Encoding.Unicode;
                        break;
                }
                return encoding;
            }
        }
        /// <summary>
        /// 配置文件
        /// </summary>
        public TCPClientConfig Config { get; set; } = new TCPClientConfig();
        public SerializableDictionary<string, string> Commands { get; set; }

        [XmlIgnore]
        /// <summary>
        /// 连接状态
        /// </summary>
        public bool IsConnected
        {
            get
            {
                if (_client == null)
                    return false;
                return _client.Connected;
            }
        }

        #endregion

        #region Event
        /// <summary>
        /// 连接事件
        /// </summary>
        public event EventHandler<TcpClientStateEventArgs> Connected;
        protected void OnConnected()
        {
            if (Connected != null)
                Connected(this, new TcpClientStateEventArgs(_client));
        }
        /// <summary>
        /// 断开事件
        /// </summary>
        public event EventHandler<TcpClientStateEventArgs> Disconnected;
        protected void OnDisconnected()
        {
            if (Disconnected != null)
                Disconnected(this, new TcpClientStateEventArgs(_client));
        }
        /// <summary>
        /// 接收事件
        /// </summary>
        public event EventHandler<TcpClientDatagramReceivedEventArgs<byte[]>> DatagramReceived;

        protected void OnDatagramReceived(TcpClientDatagramReceivedEventArgs<byte[]> e)
        {
            if (DatagramReceived != null)
                DatagramReceived(this, e);
        }
        /// <summary>
        /// 运行显示事件
        /// </summary>
        public event Action<TCPClient, string> CommRunDisplay;
        protected void OnCommRunDisplay(TCPClient client, string str)
        {
            if (CommRunDisplay != null)
                CommRunDisplay(client, str);
        }
        [XmlIgnore]
        public ConcurrentQueue<string> ReceivedStringQueue { get; set; } = new ConcurrentQueue<string>();
        #endregion

        #region Constructor
        public TCPClient()
        {
            _client = new TcpClient();
            Commands = new SerializableDictionary<string, string>();    
        }
        public TCPClient(string name):this()
        {
            Name = name;
        }
        public TCPClient(string _ip, string _port, string _name = "") : this()
        {
            IPAddress iP; int port;
            if (IPAddress.TryParse(_ip, out iP))
                Config.IP = _ip;
            if (int.TryParse(_port, out port))
                Config.Port= port;
            Name = _name;
        }
        public TCPClient(IPAddress ipAddress, string _port, string _name = "") : this()
        {
            int port;
            if (int.TryParse(_port, out port))
                Config.Port = port;
            Config.IP = ipAddress.ToString();
            Name = _name;
        }
        #endregion

        #region Method
        public bool Connect()
        {
            try
            {
                if (!IsConnected)
                {
                    if (_client != null)
                    {
                        _client = new TcpClient();
                        _client.Connect(IPAddress.Parse(IP), this.Port);
                        if (IsConnected)
                        {
                            OnConnected();
                            OnCommRunDisplay(this, $"{Name}连接服务器成功:{this._client.Client.RemoteEndPoint.ToString()}");
                            byte[] buffer = new byte[this._client.ReceiveBufferSize];
                            TcpClientState clientState = new TcpClientState(_client, buffer);
                            clientState.NetworkStream.BeginRead(clientState.Buffer, 0, buffer.Count(), new AsyncCallback(HandleTcpServerReceive), clientState);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                OnCommRunDisplay(this, $"{Name}连接服务器失败,原因：{ex.Message}");
            }
            return IsConnected;
        }
        private void HandleTcpServerReceive(IAsyncResult ar)
        {
            try
            {
                if (IsConnected)
                {
                    TcpClientState socket_Client = ar.AsyncState as TcpClientState;
                    int num = 0;
                    try
                    {
                        num = socket_Client.NetworkStream.EndRead(ar);
                    }
                    catch(Exception ex)
                    {
                        LogHelper.Instance.Log("TcpClientInner", "TcpClient接收异常!", MessageLevel.Error, ex);
                    }
                    if (num == 0)
                    {
                        OnCommRunDisplay(this, $"{Name}客户端连接断开！");
                        OnDisconnected();
                        _client.Close();
                        return;
                    }
                    byte[] receivedBytes = new byte[num];
                    Buffer.BlockCopy(socket_Client.Buffer, 0, receivedBytes, 0, num);
                    receiveDataByte = receivedBytes;
                    receiveDataStr = ReceiveEncoding.GetString(receivedBytes);
                    TcpClientDatagramReceivedEventArgs<byte[]> e = new TcpClientDatagramReceivedEventArgs<byte[]>(Name, receiveDataStr, receiveDataByte);
                    OnDatagramReceived(e);
                    OnMessageReceived?.Invoke(receiveDataStr);
                    ReceivedStringQueue.Enqueue(receiveDataStr);
                    OnCommRunDisplay(this, $"{Name}-->：{receiveDataStr}");
                    ReceiveFinishEvent.Set();
                    socket_Client.NetworkStream.BeginRead(socket_Client.Buffer, 0, socket_Client.Buffer.Length, new AsyncCallback(HandleTcpServerReceive), socket_Client);
                }
            }
            catch (Exception ex)
            {
                Disconnect();
                OnCommRunDisplay(this, $"{Name}客户端连接断开！");
                LogHelper.Instance.Log("TcpClientInner", "TcpClient接收异常!", MessageLevel.Error, ex);
            }
        }
        public bool Disconnect()
        {
            bool result = false;
            ReceiveFinishEvent.Set();
            try
            {
                if (_client != null)
                {
                    if (IsConnected)
                        _client.Close();
                    _client.Client.Dispose();
                    OnCommRunDisplay(this, $"{Name}客户端连接断开！");
                    OnDisconnected();
                    result = true;
                }
            }
            catch
            {
            }
            return result;
        }
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!IsDisposed)
            {
                if (disposing)
                {
                    if (_client != null)
                    {
                        _client.Close();
                    }
                }
                IsDisposed = true;
            }
        }
        protected void ResetReceiveValue()
        {
            ReceiveFinishEvent.Set();
            receiveDataByte = new byte[] { };
            receiveDataStr = string.Empty;
        }
        protected void ResetSendValue()
        {
            ReceiveFinishEvent.Reset();
            receiveDataByte = new byte[] { };
            receiveDataStr = string.Empty;
        }
        public bool Send(byte[] data, out string receive, int timeout = 500)
        {
            bool result = false;
            receive = string.Empty;
            if (Send(data))
            {
                if (timeout <= 0)
                {
                    if (ReceiveFinishEvent.WaitOne(500))
                    {
                        receive = receiveDataStr;
                        result = true;
                    }
                }
                else
                {
                    if (ReceiveFinishEvent.WaitOne(timeout))
                    {
                        receive = receiveDataStr;
                        result = true;
                    }
                }
            }
            return result;
        }
        public bool Send(byte[] data, out byte[] receive, int timeout = 500)
        {
            bool result = false;
            receive = new byte[] { };
            if (Send(data))
            {
                if (timeout <= 0)
                {
                    if (ReceiveFinishEvent.WaitOne(500))
                    {
                        receive = receiveDataByte;
                        result = true;
                    }
                }
                else
                {
                    if (ReceiveFinishEvent.WaitOne(timeout))
                    {
                        receive = receiveDataByte;
                        result = true;
                    }
                }
            }
            return result;
        }
        public bool Send(byte[] data)
        {
            if (data == null)
                throw new ArgumentNullException("datagram");
            bool result = false;
            try
            {
                if (this.IsConnected)
                {
                    if (_client != null)
                    {

                        lock (objLock)
                        {
                            ResetSendValue();
                            result = (_client.Client.Send(data) >= 0);
                            if (result)
                                OnCommRunDisplay(this, $" {Name}->:{data.ByteArrayToString()}");
                            else
                            {
                                ResetReceiveValue();
                                OnCommRunDisplay(this, $" {Name}->发送失败:{data.ByteArrayToString()}");
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ResetReceiveValue();
                OnCommRunDisplay(this, $" {Name}->发送失败:{data.ByteArrayToString()}");
            }
            return result;
        }
        public bool Send(string data, out string receive, int timeout = 500)
        {
            bool result = false;
            receive = string.Empty;
            
            if (Send(data))
            {
                if (timeout <= 0)
                {
                    if (ReceiveFinishEvent.WaitOne(500))
                    {
                        ReceivedStringQueue.TryDequeue(out receive);
                        result = true;
                    }
                }
                else
                {
                    if (ReceiveFinishEvent.WaitOne(timeout))
                    {
                        ReceivedStringQueue.TryDequeue(out receive);
                        result = true;
                    }
                }
            }
            return result;
        }
        public bool Send(string data, out byte[] receive, int timeout = 500)
        {
            bool result = false;
            receive = new byte[] { };
            if (Send(data))
            {
                if (timeout <= 0)
                {
                    if (ReceiveFinishEvent.WaitOne(500))
                    {
                        receive = receiveDataByte;
                        result = true;
                    }
                }
                else
                {
                    if (ReceiveFinishEvent.WaitOne(timeout))
                    {
                        receive = receiveDataByte;
                        result = true;
                    }
                }
            }
            return result;
        }
        public bool Send(string data)
        {
            if (data == null)
                throw new ArgumentNullException("datagram");
            bool result = false;
            try
            {
                if (this.IsConnected)
                {
                    if (_client != null)
                    {
                        lock (objLock)
                        {
                            ResetSendValue();
                            LogHelper.Instance.Log("system", data, MessageLevel.Log);
                            byte[] datas = SendEncoding.GetBytes(data);
                            result = (_client.Client.Send(datas) >= 0);
                            if (result)
                                OnCommRunDisplay(this, $" {Name}->:{data}");
                            else
                            {
                                ResetReceiveValue();
                                OnCommRunDisplay(this, $" {Name}->发送失败:{data}");
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ResetReceiveValue();
                OnCommRunDisplay(this, $" {Name}->发送失败:{data}");
            }
            return result;
        }
        public string GetRemoteEndPoint()
        {
            return _client.Client.RemoteEndPoint.ToString();
        }
        public void EmptyBuffer()
        {
            while(!ReceivedStringQueue.IsEmpty)
            {
                string temp = "";
                ReceivedStringQueue.TryDequeue(out temp);
            }
        }
        #endregion

        #region ICommunicationShow
        public CommTypeEnum CommType { get; } = CommTypeEnum.TCPClient;

        public string Name { get; set; } = string.Empty;
        public string Module { get; set; }
        public bool IsOpenFlag => IsConnected;

        /// <summary>
        /// 指令合集
        /// </summary>
        [XmlIgnore]
        public SerializableDictionary<string, string> SendCmd
        {
            get
            {
                return Config.SendCmd;
            }
            set
            {
                Config.SendCmd = value;
            }
        }
        [XmlIgnore]
        public Action<string> OnMessageReceived { get; set; }

        public object GetConfig()
        {
            return Config;
        }

        public Form GetDebugForm()
        {
            return new CommunicationPage(this);
        }

        public List<string> GetShowList()
        {
            return new List<string>();
        }

        public string GetTreeNodeShowStr()
        {
            return $"[TCPClient]({IP}:{Port})";
        }

        public void SetConfig(object config)
        {
            if (config is TCPClientConfig)
            {
                Config = config as TCPClientConfig;
            }
        }
        public void SetOpenOrClose(OpenOrClose openOrClose)
        {
            switch (openOrClose)
            {
                case OpenOrClose.Open:
                    Connect();
                    break;
                case OpenOrClose.Close:
                    Disconnect();
                    break;
            }
        }

        void ICommunication.Send(string message)
        {
            Send(message);
        }

        public bool DoConnect()
        {
            if (IsConnected)
                return true;
            return Connect();
        }
        #endregion
    }
}
